import { Cls } from "../util/Defined";
import { ECSErr } from "../err/ECSErr";
import { ArrUtil } from "../util/ArrUtil";
import { TypeUtil } from "../util/TypeUtil";
import { ECSScene, ECSSceneState } from "./ECSScene";
import { ECSSys } from "./ECSSys";

/**
 * [ECS]系统管理
 */
export class ECSSysMgr {

    /** 所有系统 */
    protected _sysMap = new Map<Cls<ECSSys>, ECSSys>();
    /** 系统队列 */
    protected _systems: ECSSys[] = [];
    /** 系统发生变化 需要重新排序 */
    protected _dirty: boolean = true;

    constructor(
        /** 所在场景 */
        readonly scene: ECSScene
    ) { }


    /**
     * 获取系统
     * @param sysType 系统类型
     */
    get<T extends ECSSys>(sysType: Cls<T>) {
        return this._sysMap.get(sysType) as T;
    }

    /**
     * 添加系统
     * @param sysType 系统类型
     */
    add<T extends ECSSys>(sysType: Cls<T>) {
        if (this._sysMap.has(sysType)) {
            throw new ECSErr("重复添加系统");
        }
        this._dirty = true;
        const sys = new sysType(this.scene);
        this._sysMap.set(sysType, sys);
        this._systems.push(sys);
        sys.filter = this.scene.filterMgr.genFilter(sys.ifilter);
        if (this.scene.state !== ECSSceneState.init) {
            sys.onAdd();
        }
        return sys as T;
    }

    /**
     * 移除系统
     * @param sys 系统
     */
    remove(sys: ECSSys) {
        if (this._sysMap.delete(TypeUtil.getCls(sys))) {
            ArrUtil.remove(this._systems, sys);
            sys.onRemove();
            this.scene.offCaller(sys);
            sys.filter.offCaller(sys);
            this.scene.filterMgr.removeFilter(sys.filter);
            return true;
        }
        return false;
    }

    /**
     * 场景启动时触发
     */
    onStart() {
        this.sort();
        this._systems.forEach(sys => sys.onAdd());
    }

    /**
     * 根据系统更新顺序进行排序
     */
    sort() {
        if (this._dirty) {
            this._systems.sort((a, b) => a.updateOrder - b.updateOrder);
        }
    }

    /**
     * 帧循环更新系统(前)
     */
    beforeUpdate() {
        this._sysMap.forEach(sys => sys.onBeforeUpdate());
    }

    /**
     * 帧循环更新系统
     */
    update() {
        this._sysMap.forEach(sys => sys.onUpdate());
    }

    /**
     * 帧循环更新系统(后)
     */
    lateUpdate() {
        this._sysMap.forEach(sys => sys.onLateUpdate());
    }

    /**
     * 场景停止时销毁
     */
    destroy() {
        this._sysMap.forEach(sys => sys.onRemove());
        this._sysMap.clear();
        this._systems.length = 0;
    }

    // ============ DEBUG ============

    /**
     * 开启所有系统的调试运行时间记录
     */
    enableDebugRecordAll() {
        this._sysMap.forEach(sys => sys.enableDebugRecord());
    }

    /**
     * 调试报告
     */
    getDebugReport() {
        const records: {
            sys: ECSSys,
            records: Map<string, {
                count: number;
                least: number;
                total: number;
                avg: number;
            }>
        }[] = [];
        this._sysMap.forEach(sys => sys.debugRecord && records.push({ sys, records: sys.debugRecord }));
        return records.sort((a, b) => b.records.get("total").total - a.records.get("total").total);
    }

    /**
     * 打印调试报告
     */
    printDebugReport() {
        const report = this.getDebugReport();
        let str = "";
        str += `\x1B[31m============ 系统调试报告 ============\x1B[0m\n`;
        str += `\n`;
        str += `已运行时间：${this.scene.runTime}s\n`;
        str += `已运行帧数：${this.scene.runFrame}\n`;
        str += `\n`;
        report.forEach(sys => {
            str += `\x1B[36m${TypeUtil.getCls(sys.sys).name}\x1B[0m\n`;
            sys.records.forEach((record, key) => {
                str += `\x1B[33m${key}\x1B[0m ${Array.from({ length: 14 - key.length }, () => " ").join("")} 单次：\x1B[32m${record.least.toFixed(6)}ms\x1B[0m 平均：\x1B[32m${record.avg.toFixed(6)}ms\x1B[0m\n`;
            })
        });
        str += `\n`;
        str += `\x1B[31m============ 系统调试报告 ============\x1B[0m\n`;
        this.scene.logger.log(str);
    }

}